package com.micro.frog.job.utils;

import com.alibaba.fastjson.JSON;
import com.micro.frog.core.exception.MaException;
import com.micro.frog.job.model.entity.Job;
import com.micro.frog.job.service.JobService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.util.Iterator;
import java.util.List;

/**
 * 任务工具类
 * 此系统  统一采用  表达式策略触发
 * 即: 使用 Trigger 的子类 CronTrigger 实现， 而不使用默认的SimpleTrigger
 * 所有 Trigger 触发器 都使用 CronScheduleBuilder 构建
 *
 * @author :  ljh
 * @date : 2018/5/8
 **/
@Slf4j
public class ScheduleUtils {

    private static Scheduler scheduler;
    private JobService jobService;
    private SchedulerFactoryBean schedulerFactoryBean;

    protected static final String JOB_KEY = "JOB_KEY_";

    /**
     * 在spring容器初始化的时候执行该方法
     */
    @PostConstruct
    public void init() {
        this.jobService = SpringContextUtils.getBean(JobService.class);
        this.schedulerFactoryBean = SpringContextUtils.getBean(SchedulerFactoryBean.class);
        //初始化容器
        scheduler = this.schedulerFactoryBean.getScheduler();
        log.info("加载所有定时任务");
        loadAllTask();
    }

    /**
     * 加载所有的任务到容器
     */
    public void loadAllTask() {
        try {
            List<Job> jobs = jobService.all();
            log.error("当前系统定时任务数量: {}", jobs.size());
            Iterator iterator = jobs.iterator();
            while (iterator.hasNext()) {
                createSchedule((Job) iterator.next());
            }
        } catch (MaException e) {
            log.error("加载任务失败，e:{}", e);
        }
    }

    /**
     * 方法驱动调用的工厂类 指定任务执行的 JobDetailImpl
     * 注意：我们这里指定自定义的执行方法
     * 当然你也可以用事项类继承 Job 或者 QuartzJobBean 的方式 创建 JobDetail
     */
    public static void createSchedule(Job job) throws MaException {
        try {
            MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean();
            jobDetailFactoryBean.setTargetObject(SpringContextUtils.getBean(Class.forName(job.getService())));
            jobDetailFactoryBean.setTargetMethod(job.getMethod());
            jobDetailFactoryBean.setName(job.getName());
            jobDetailFactoryBean.setConcurrent(false);
            Object[] arguments = new Object[1];
            arguments[0] = job;
            jobDetailFactoryBean.setArguments(arguments);
            //此方法已经生成了 JobDetail实例
            jobDetailFactoryBean.afterPropertiesSet();
            JobDetail jobDetail = jobDetailFactoryBean.getObject();
            //实例 Trigger 触发器 用来部署任务的执行策略(表达式或者间隔)
            TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger();
            triggerBuilder.withIdentity(getTriggerKey(job.getName()));
            //初始化 表达式实例
            CronExpression cronExpression = new CronExpression(job.getCron());
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            triggerBuilder.withSchedule(cronScheduleBuilder);
            Trigger trigger = triggerBuilder.build();
            //把 Job 注册到 Scheduler 容器中 一个 Job 对应一个 Trigger ， 一个 Trigger 可以对应多个Job
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (ClassNotFoundException e) {
            log.error("--{}加载失败,任务实现类找不到。e:{}", job.getName(), e);
            throw new MaException(job.getName() + "创建失败,任务实现类找不到");
        } catch (NoSuchMethodException e) {
            log.error("--{}加载失败,任务实现类方法找不到。e:{}", job.getName(), e);
            throw new MaException(job.getName() + "创建失败,任务实现类方法找不到");
        } catch (ParseException e) {
            log.error("--{}加载失败,表达式解析失败。e:{}", job.getName(), e);
            throw new MaException(job.getName() + "创建失败,表达式解析失败");
        } catch (ObjectAlreadyExistsException e) {
            log.error("--{}加载任务失败,任务已存在。e:{}", job.getName(), e);
            throw new MaException(job.getName() + "创建失败,任务名称已存在");
        } catch (SchedulerException e) {
            log.error("--{}加载任务失败,创建Scheduler容器失败。e:{}", job.getName(), e);
            throw new MaException(job.getName() + "创建失败,创建容器失败");
        }
    }

    /**
     * 立即执行任务
     */
    public static void runJob(Job job) throws MaException, SchedulerException {
        try {
            //恢复容器中所有任务
            scheduler.resumeAll();
            //重组任务
            rescheduleJob(job);
            //立即执行任务
            scheduler.triggerJob(getJobKey(job.getName()));
        } catch (MaException e) {
            log.error("执行任务失败，e:{}", e);
            throw e;
        }
    }

    /**
     * 重组任务
     *
     * @param job
     */
    public static void rescheduleJob(Job job) throws SchedulerException, MaException {
        //通过触发器 Key 去找到 Job 然后执行
        TriggerKey triggerKey = getTriggerKey(job.getName());
        boolean exist = scheduler.checkExists(triggerKey);
        //按新的表达式  重新构建 Trigger 策略
        if (exist) {
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCron());
            //获取容器中的 触发器
            CronTrigger trigger = getCronTrigger(job.getName());
            trigger = trigger.getTriggerBuilder()
                    .withIdentity(getTriggerKey(job.getName())).startNow()
                    .withSchedule(cronScheduleBuilder).build();
            //重新安排工作策略
            JobDetail jobDetail = scheduler.getJobDetail(getJobKey(job.getName()));
            log.info("任务内容：{}", JSON.toJSONString(jobDetail));
            scheduler.rescheduleJob(getTriggerKey(job.getName()), trigger);

        } else {
            log.info("任务不存在");
            throw new MaException("任务不存在");
        }
    }

    public static void checkExist(String key) throws MaException {
        try {
            if (scheduler.checkExists(getJobKey(key))) {
                throw new MaException("任务名称已存在");
            }
        } catch (SchedulerException e) {
            throw new MaException("任务异常");
        }
    }

    /**
     * 获取容器中 表达式触发器
     *
     * @param triggerKey
     * @return
     */
    public static CronTrigger getCronTrigger(String triggerKey) throws SchedulerException {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(triggerKey));
        } catch (SchedulerException e) {
            log.error("生成表达式触发器失败");
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 生成容器中 触发器的 key(唯一)
     */
    public static TriggerKey getTriggerKey(String key) {
        return TriggerKey.triggerKey(key);
    }

    /**
     * 生成容器中 任务的 JobKey(唯一)
     */
    public static JobKey getJobKey(String key) {
        return JobKey.jobKey(JOB_KEY + key);
    }

}
